package cn.bonoon.kernel.io.synchronize;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.SocketException;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

import cn.bonoon.kernel.util.StringHelper;

/**
 * 通过FTP服务器把指定的目录与FTP上的目录进行同步处理
 * @author jackson
 *
 */
public class FtpFileSynchronization extends AbstractFileSynchronizeServer<FTPClient>{
	
	private final static int DEFAULT_FTP_PORT = 21;

	private boolean passiveMode = true;
	private boolean binaryTransfer = true;  
	
	public FtpFileSynchronization(){
		port = DEFAULT_FTP_PORT;
	}
	
	@Override
	protected void internalExecute(File localFile) throws Exception {

		FTPClient ftpClient = getFTPClient();
		if(null == ftpClient || !ftpClient.isConnected()){
			throw new NeedExitTaskException(host, port);
		}
		
		if(upload(ftpClient, localFile)){
			//成功
		}
	}
	
	private boolean upload(FTPClient ftpClient, File localFile) throws Exception{
		String remotePath = localFile.getPath().substring(rootPathLength);
		//对文件名进行编码处理
		remotePath = new String(remotePath.getBytes("GBK"),"iso-8859-1");
		int lp = remotePath.lastIndexOf('/');
		String remoteFilename;
		if(lp != -1){
			String remoteDirectory = remotePath.substring(0, lp + 1);
			if(!createDirectory(ftpClient, remoteDirectory)){
				throw new Exception("FTP服务器无法切换到指定目录[" + remoteDirectory + "]");
			}
			remoteFilename = remotePath.substring(lp + 1);
		}else{
			remoteFilename = remotePath;
		}
		FTPFile[] remoteFiles = ftpClient.listFiles(remoteFilename);
		
        long remoteFilesize = 0L;
        if(remoteFiles.length > 0){   
        	remoteFilesize = remoteFiles[0].getSize();
        	
        	if(remoteFilesize >= localFile.length()){
	        	log.info("文件[" + remotePath + "]已经存在！");
	        	return false;
        	}
        }   

		return uploadFile(remoteFilename, localFile, ftpClient, remoteFilesize);
	}
	
	public boolean uploadFile(String remoteFilename,File localFile,FTPClient ftpClient,long remoteSize) throws IOException{   
        try(RandomAccessFile raf = new RandomAccessFile(localFile, "r");   
	        OutputStream out = ftpClient.appendFileStream(remoteFilename)){
	        
	        //断点续传，从已经上传完的点开始重新上传
	        if(remoteSize > 0){   
	            ftpClient.setRestartOffset(remoteSize);   
	            raf.seek(remoteSize);   
	        }   
	        
	        byte[] bytes = new byte[BUFFERED_SIZE];   
	        int c;   
	        while((c = raf.read(bytes)) != -1){   
	            out.write(bytes, 0, c);   
	        }   
	        out.flush();     
        }
        return ftpClient.completePendingCommand();   
    }   
	
	public boolean createDirectory(FTPClient ftpClient, String remoteDirectory) throws IOException{   
        if(!remoteDirectory.equalsIgnoreCase("/") && !ftpClient.changeWorkingDirectory(remoteDirectory)){   
            //如果远程目录不存在，则递归创建远程服务器目录   
            int start = remoteDirectory.startsWith("/") ? 1 : 0, end = 0;   
            
            while(true){   
            	end = remoteDirectory.indexOf("/", start); 
            	String subDirectory = null;
            	if(end == -1){
            		//最后一个没有出现"/"的情况
            		subDirectory = remoteDirectory.substring(start);
            	}
            	
            	if(end > start){
	                subDirectory = remoteDirectory.substring(start, end);
            	}else break;
            	
                if(!ftpClient.changeWorkingDirectory(subDirectory)){   
                    if(ftpClient.makeDirectory(subDirectory)){   
                        ftpClient.changeWorkingDirectory(subDirectory);   
                    }else {   
                    	log.error("在FTP服务器上创建目录[" + remoteDirectory + "]失败!");
                        return false;   
                    }
                }
            	
                start = end + 1;   
            }   
        }
        return true;   
    }   
	
	private FTPClient getFTPClient() throws Exception {
		FTPClient ftpClient = clientThreadLocal.get();
		
        if (ftpClient == null || !ftpClient.isConnected()) {  
            ftpClient = new FTPClient(); //构造一个FtpClient实例  
            ftpClient.setControlEncoding(encoding); //设置字符集 
            
            //连接到ftp服务器  
            if(!connect(ftpClient)) return null;
      
            //设置为passive模式  
            if (passiveMode) {  
                ftpClient.enterLocalPassiveMode();  
            }  
            setFileType(ftpClient); //设置文件传输类型  
      
            try {  
                ftpClient.setSoTimeout(connectedTimeout);  
            } catch (SocketException e) {  
                throw new Exception("Set timeout error.", e);  
            }  
            clientThreadLocal.set(ftpClient);  
        }  
        
        //把ftp的目录切换到指定的目录下
        if(StringHelper.isNotEmpty(targetRootPath)){
        	ftpClient.changeWorkingDirectory(targetRootPath);
        }
        
        return ftpClient;  
    }  
	
	private void setFileType(FTPClient ftpClient) throws Exception {  
        try {  
            if (binaryTransfer) {  
                ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);  
            } else {  
                ftpClient.setFileType(FTPClient.ASCII_FILE_TYPE);  
            }  
        } catch (IOException e) {  
            throw new Exception("Could not to set file type.", e);  
        }  
    }  
	
	private boolean connect(FTPClient ftpClient) throws Exception{
        ftpClient.connect(host, port);    
        if(FTPReply.isPositiveCompletion(ftpClient.getReplyCode())){   
            if(ftpClient.login(username, password)){   
            	log.info(host + "[" + port + "] FTP服务器连接成功...");
                return true;   
            }   
        }   
        closeTask();
        return false;
    }  
	
	@Override
	public void closeTask() throws Exception{   
		FTPClient ftpClient = clientThreadLocal.get();
		if(null != ftpClient){
	        ftpClient.logout();
	        if(ftpClient.isConnected()){   
	            ftpClient.disconnect();   
	        }   
	        ftpClient = null;//释放内存
	        
	        log.info("关闭FTP服务器" + host + "[" + port + "]");
		}
    }

}
